var letters = $A($R('a','h')); 
var board, t = new Test.Unit.Assertions();;
Event.observe(window, 'load', function() {
	board = new Board();
});

var Board = Class.create({

    initialize: function() {
        var colors = ['white','black'];
        var currentColor = 0;
        
        this.p1 = new Player(this,'white');
        this.p2 = new Player(this,'black');
        this.squares = $H();
        this.squaresById = $H();
        this.piecesById = $H();
        this.lastPieceId = 1;
        this.lastPlayedPiece = null;
        
        // Add some elements
        this.noticeElement = Builder.node('p',{id:'notice'});
        this.playerElement = Builder.node('p',{id:'player'});
        this.toolbarElement = Builder.node('p',{id:'toolbar'},[
            Builder.node('p',{id:'debug'}),
            Builder.node('a',{href:'javascript:window.location.reload();'},'New game'),
            Builder.node('a',{href:'javascript:board.revertMove();'},'Revert move'),
            Builder.node('a',{href:'javascript:board.runAutomaticGame();'},'Run automatic test mode.')]);
        $('chess').appendChild(Builder.node('div',{id:'info'},[this.playerElement, this.noticeElement, this.toolbarElement]));
        this.element = $('chess').appendChild(Builder.node('div',{id:'board'}));
        
        // Build the board
        $A($R(1,8)).reverse().each(function(rowNumber) {
            this.squares[rowNumber] = $H();
            letters.each(function(colLetter) {
                var isFirst = false;
                if (colLetter == letters.first())
                    isFirst = true;
                this.squares[rowNumber][colLetter] = new Square(this,rowNumber,colLetter,colors[currentColor],isFirst);
                this.element.appendChild(this.squares[rowNumber][colLetter].element);
                this.squaresById.set(this.squares[rowNumber][colLetter].element.id, this.squares[rowNumber][colLetter]);
               currentColor = 1 - currentColor;
            }.bind(this));
            currentColor = 1 - currentColor;
        }.bind(this));
                
        // Pawns
        $R(1,8).each(function(row) {
            new Pawn(this,this.p1,this.squares[2][letters[row-1]]);
            new Pawn(this,this.p2,this.squares[7][letters[row-1]]);
        }.bind(this));
        
        // Rooks
        new Rook(this,this.p1,this.squares[1]['a']);
        new Rook(this,this.p1,this.squares[1]['h']);
        new Rook(this,this.p2,this.squares[8]['a']);
        new Rook(this,this.p2,this.squares[8]['h']);
        
        // Knights
        new Knight(this,this.p1,this.squares[1]['b']);
        new Knight(this,this.p1,this.squares[1]['g']);
        new Knight(this,this.p2,this.squares[8]['b']);
        new Knight(this,this.p2,this.squares[8]['g']);
    
        // Bishops
        new Bishop(this,this.p1,this.squares[1]['c']);
        new Bishop(this,this.p1,this.squares[1]['f']);
        new Bishop(this,this.p2,this.squares[8]['c']);
        new Bishop(this,this.p2,this.squares[8]['f']);
        
        // Queens
        new Queen(this,this.p1,this.squares[1]['d']);
        new Queen(this,this.p2,this.squares[8]['d']);

        // Kings
        new King(this,this.p1,this.squares[1]['e']);
        new King(this,this.p2,this.squares[8]['e']);
        
        this.changeTurn();
    },
    
    otherPlayer: function(player) {
        return this.p1 == player ? this.p2 : this.p1;
    },
    
    notice: function(message) {
        this.noticeElement.innerHTML = message;
    },
    
    changeTurn: function(message) {
        this.notice(message || '');
        if (this.currentPlayer) {
            this.currentPlayer = this.otherPlayer(this.currentPlayer);
        } else {
            this.currentPlayer = this.p1;
        }
        this.playerElement.innerHTML = this.currentPlayer.color.capitalize() + "'s turn.";
    },
    
    revertMove: function() {
        var eatenPiece = this.lastPlayedPiece.square.previousPiece;
        var otherPiece = this.lastPlayedPiece;
        var square1 = this.lastPlayedPiece.square;
        var square2 = this.lastPlayedPiece.previousSquare;
        this.lastPlayedPiece.moveBackTo(square2);
        if (eatenPiece && eatenPiece != this.lastPlayedPiece && eatenPiece.square == null) {
            eatenPiece.moveBackTo(square1);
            this.lastPlayedPiece = otherPiece;
        }
        this.changeTurn();
    },
    
    toHash: function() {
        var result = $H();
        this.squaresById.each(function(pair){
            result.set(pair.key, pair.value.piece ? pair.value.piece.name : 'empty');
        });
        return result;
    },
    
    runAutomaticGame: function() {
        var pieces = this.piecesById.values().select(function(piece){return piece.hasBeenEaten == false && piece.player == this.currentPlayer;}.bind(this));
        var i = Math.floor(Math.random()*pieces.length);
        var j = i;
        var b = false;
        
        do {
            var piece = pieces[j];
            var moves = piece.allowedMoves();
            if (moves.length > 0) {
                var move = moves[Math.floor(Math.random()*moves.length)];
                this.squaresById.get(move).pieceDropped(piece.element);
                b = true;
                break;
            }
            j = (j+1) % pieces.length;
            if (i == j)
                b = false;
        } while (!b)
        
        if (b) {
            $('debug').innerHTML = t.summary();
            window.setTimeout(this.runAutomaticGame.bind(this),100);
        }
        
    },

});

var Square = Class.create({
    
    initialize: function(board, row, col, color, isFirst) {
        var classes = 'square '+color;
        if ( isFirst )
            classes += ' first';
        
        this.board = board;
        this.row = row;
        this.col = col;
        this.element = Builder.node('div',{className:classes, id:'square_'+col+row},'');
        this.piece = null;
        this.previousPiece = null;
        this.notice = '';
        Droppables.add(
            this.element,{
                accept:this.element.id,
                onDrop: this.pieceDropped.bind(this)
        });
        
    },
    
    pieceDropped: function(pieceElement) {
        var piece = this.board.piecesById.get(pieceElement.id);
        t.assertNotNull(piece, "Null piece dropped");
        
        piece.moveTo(this);
        t.assert(this.piece = piece, "Piece moved to a square, but not in the square after move.");
          
        if (piece.player.isCheck()) {
            this.board.revertMove();
            this.board.changeTurn("Check: forbidden move.");
        } else {
            this.board.changeTurn(this.notice);
            this.notice = '';
            if (this.board.currentPlayer.isCheck())
                this.board.notice("Check!");
        }
    },
    
    eatPiece: function() {
        t.assertNotNull(this.piece, "Trying to eat null piece.");
        this.piece.hasBeenEaten = true;
        this.piece.element.remove();
        this.previousPiece = this.piece;
        this.piece.square = null;
        this.piece.previousSquare = this;
        this.piece = null;
        this.notice = "Eaten";
    },
    
    up: function() {
        if (this.row == 8)
            return null;
        return this.board.squares[this.row+1][this.col];
    },
    
    down: function() {
        if (this.row == 1)
            return null;
        return this.board.squares[this.row-1][this.col];
    },
    
    left: function() {
        if (this.col == 'a')
            return null;
        return this.board.squares[this.row][letters[letters.indexOf(this.col)-1]];
    },
    
    right: function() {
        if (this.col == 'h')
            return null;
        return this.board.squares[this.row][letters[letters.indexOf(this.col)+1]];
    }
    
});

var Player = Class.create({
   
    initialize: function(board,color) {
        this.board = board;
        this.color = color;
        this.pieces = [];
        this.king = null;
    },

    getKing: function() {
        if (!this._king)
            this.pieces.each(function(piece){
                if (piece.name=='king')
                    this._king = piece;
            }.bind(this));
        return this._king;
    },

    isCheck: function() {
		return this.board.otherPlayer(this).pieces.inject([],function(moves, piece){return moves.concat(piece.allowedMoves())}).include(this.getKing().square.element.id);
    },
    
});

var Piece = Class.create({
    
    initialize: function(board, player, square) {
        t.assertNull(square.piece, "Trying to add a piece in an occupied square.")
        
        this.board = board;
        this.player = player;
        this.square = square;
        this.previousSquare = null;
        this.element = null;
        this.hasBeenEaten = false;
        
        this.player.pieces.push(this);
        
        this.element = Builder.node('img',{src: 'pieces/'+this.name+'_'+this.player.color+'.png', className:'piece '+this.name, id:'p'+this.board.lastPieceId++});
        this.board.piecesById.set(this.element.id,this);
        this.moveTo(this.square);
        
        new Draggable(this.element,{
            snap:[45,45],
            revert:true,
            onStart: function(drag) {
                this.element.className.split(' ').each(function(className){
                    if (className.startsWith('square_'))
                        this.element.removeClassName(className)
                }.bind(this));
                if (this.board.currentPlayer == this.player)
                    this.allowedMoves().each(function(allowedMove){
                        this.element.addClassName(allowedMove)
                    }.bind(this));
            }.bind(this)
        });
        
    },

    allowedMoves: function() {
        return null;
    },
    
    moveTo: function(square) {
        t.assertNotNull(square, "Trying to move a piece to a null square.");
        t.assert(square.piece ? square.piece.player.color != this.player.color : true, "Trying to eat one of your own!");
        
        if (square.piece) {
            square.eatPiece();
        } else {
            square.previousPiece = null;
        }
        
        if (this.square)
            this.square.piece = null;
        square.piece = this;
        
        
        if (this.square)
            this.square.previousPiece = this;
        this.previousSquare = this.square;
        this.square = square;
        
        square.element.appendChild(this.element);
        this.element.setStyle({top:0,left:0}); 
        
        this.board.lastPlayedPiece = this;
        
    },
    
    moveBackTo: function(square) {
        if (this.hasBeenEaten)
            this.hasBeenEaten = false;
        this.moveTo(square);
    },
    
});

var Pawn = Class.create(Piece,{
    
    initialize: function($super, board, player, square) {
        this.name = "pawn";
        this.movesCount = 0;
        $super(board, player, square);
    },
    
    allowedMoves: function($super) {
		var moves = [];	
        try {if (this.player.color == 'white' && !this.square.up().piece) {
            moves.push(this.square.up().element.id);
        }} catch(e) {}
        try {if (this.player.color == 'white' && !this.square.up().up().piece && this.movesCount <= 1) {
            moves.push(this.square.up().up().element.id);
        }} catch(e) {}
		try {if (this.player.color == 'white' && this.square.up().left().piece && this.square.up().left().piece.player.color == 'black') {
            moves.push(this.square.up().left().element.id);
        }} catch(e) {}
		try {if (this.player.color == 'white' && this.square.up().right().piece && this.square.up().right().piece.player.color == 'black') {
            moves.push(this.square.up().right().element.id);
        }} catch(e) {}
        try {if (this.player.color == 'black' && !this.square.down().piece) {
            moves.push(this.square.down().element.id);
        }} catch(e) {}
        try {if (this.player.color == 'black' && !this.square.down().down().piece && this.movesCount <= 1) {
            moves.push(this.square.down().down().element.id);
        }} catch(e) {}
		try {if (this.player.color == 'black' && this.square.down().left().piece && this.square.down().left().piece.player.color == 'white') {
            moves.push(this.square.down().left().element.id);
        }} catch(e) {}
		try {if (this.player.color == 'black' && this.square.down().right().piece && this.square.down().right().piece.player.color == 'white') {
            moves.push(this.square.down().right().element.id);
        }} catch(e) {}
        return moves;
    },

    moveTo: function($super, square) {
        $super(square);
        this.movesCount += 1;
    },
    
    moveBackTo: function($super, square) {
        $super(square);
        this.movesCount -= 2;
    },
    
});

var Knight = Class.create(Piece,{
    
    initialize: function($super, board, player, square) {
        this.name = "knight";
        $super(board, player, square);
    },

	allowedMoves: function($super) {
		var moves = [];
		var directions = ['up()','down()','left()','right()'];
		var orthogonals = {
			'up()': ['left()','right()'],
			'down()': ['left()','right()'],
			'left()': ['up()','down()'],
			'right()': ['up()','down()'],
		}
		directions.each(function(direction){
			orthogonals[direction].each(function(orthogonal){
				try {
					eval('var square = this.square.'+direction+'.'+direction+'.'+orthogonal+';');
					if (!square.piece || (square.piece && square.piece.player.color != this.player.color))
						moves.push(square.element.id);
				} catch (e) {}
			}.bind(this));
		}.bind(this));
		return moves;
	}
	
    
});

var NoblePiece = Class.create(Piece,{
	allowedMovesForDirections: function(directions,maxDistance) {
		maxDistance = maxDistance === undefined ? 0 : maxDistance;
		var moves = [];
		directions.each(function(direction){
			var current = this.square;
			var i;
			for ( i=0 ; (!maxDistance || i<maxDistance) ; i++ ) {
				try {
					eval('current = current.'+direction+';');
				} catch(e) {
					break;
				}
				if ( !current )
					break;
				if ( !current.piece || current.piece.player.color!=this.player.color ) {
					moves.push(current.element.id);
				}
				if ( current.piece )
					break;
			}
		}.bind(this));
		return moves;
	}
});

var Rook = Class.create(NoblePiece,{
    
    initialize: function($super, board, player, square) {
        this.name = "rook";
        $super(board, player, square);
    },

	allowedMoves: function($super) {
		return this.allowedMovesForDirections(['up()','down()','left()','right()']);
	}
    
});

var Bishop = Class.create(NoblePiece,{
    
    initialize: function($super, board, player, square) {
        this.name = "bishop";
        $super(board, player, square);
    },

	allowedMoves: function($super) {
		return this.allowedMovesForDirections(['up().left()','up().right()','down().left()','down().right()']);
	}
    
});

var Queen = Class.create(NoblePiece,{
    
    initialize: function($super, board, player, square) {
        this.name = "queen";
        $super(board, player, square);
    },

	allowedMoves: function($super) {
		return this.allowedMovesForDirections(['up().left()','up().right()','down().left()','down().right()','up()','down()','left()','right()']);
	}
    
});

var King = Class.create(NoblePiece,{
    
    initialize: function($super, board, player, square) {
        this.name = "king";
        $super(board, player, square);
    },

	allowedMoves: function($super) {
		return this.allowedMovesForDirections(['up().left()','up().right()','down().left()','down().right()','up()','down()','left()','right()'],1);
	}
    
});
